package hu.arguscab.ai;

import hu.arguscab.game.CompoundAction;

/**
 * @author sharp
 */
public class DecisionTree extends TreeImpl<CompoundAction, Model> {

	private Fitnessor fitnessor;

	public DecisionTree() {
	}

	/**
	 * @return
	 * Returns with the path that have the best Fitness Values. The Depth of that Path are 
	 * determined by <code>Terminator</code> class's <code>isTerminate</code> method.
	 */
	public Path<CompoundAction> makeDecision() {
		// (1) Evaluating tree, setting Edge's costs.
		evaulateTree();

		// (2) Assigning Fitness Values to Nodes from leaves upwards.
		calcFitnessFromLeaves();

		// (3) Creating the Path.
		return calcPath();
	}

	/**
	 * Evaluate the tree.
	 */
	private void evaulateTree() {
		evaulate();
	}

	/**
	 * Assign Fitness Value to leaves then head upwards to the tree and do so by determine MAX values
	 * of Node's children.
	 */
	private void calcFitnessFromLeaves() {
		assert fitnessor != null;

		float fitness;
		for ( Node<Model> node : leafFirstIterator() ) {
			Model m = node.getData();
			if ( hasChild(node) == false ) { // LEAF
				fitness = fitnessor.calcF(node.getData());
				// nem kell null ellenőrzés a getEdge-re, mert mindig nemnull
				fitness -= getEdge(getParent(node), node).getWeight();
			} else {						 // Parent
				fitness = getMaxFitnessFromChilds(node);
			}
			m.setFitness(fitness);
		}
	}

	/**
	 * A gyökértől kezdve a legkedvezőbb úton haladva eljut egy levélig.
	 * @return A gyökértől a legkedvezőbb levélig vezető út
	 */
	private Path<CompoundAction> calcPath() {
		Path<CompoundAction> path = new Path<CompoundAction>();
		Node<Model> start = null;
		Edge<CompoundAction> edge;
		Node<Model> from = null;
		for ( Node<Model> node : bestSearchIterator() ) {
			edge = getEdge(start, node);
			if ( edge != null )
				path.add( edge.getData());
			start = node;
		}
		return path;
	}

	/**
	 * Megkeresi a gyermekek közül a legnagyobb fitness értéket
	 * @TODO ne csak az értéket, de az utat is rögzítse
	 * @param node
	 * @return
	 */
	private float getMaxFitnessFromChilds(Node<Model>  node) {
		float max = 0;
		for ( Node<Model> n : childs(node) ) {
			Model m = n.getData();
			if ( m.getFitness() > max )
				max = m.getFitness();
		}
		return max;
	}

	/**
	 * Csak olyan fákon lehet meghívni, amelyek node-jai rendelkeznek fitnessel
	 * A gyermekek közl csak azzal az eggyel tér vissza, amelyiknek legjobb
	 * a fitness értéke
	 * @return
	 */
	public Iterable<Node<Model>> bestSearchIterator() {
		return new BestFirstSearchIterator<CompoundAction>(this);
	}

	public void setFitnessor(Fitnessor fitnessor) {
		this.fitnessor = fitnessor;
	}

	
}
